package ru.galiauir.engine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ru.galiauir.items.User;
import ru.galiauir.utils.RandomUtils;

public class Engine {

	private float alpha;
	private float beta;//alpha for buyers distribution
	private long averageAmount;
	private long dispersia;
	private User root;
	private Map<Long, User> userMap;
	private int yRatio;
	private float basicCost;

	private long totalPurchaseAmount;
	private long totalPurchaseNumber;
	private long totalBonusGiven;
	private long totalBonusSpent;

	private long monthCost;//себестоимость = сумма покупки * basicCost + потраченый бонус
	private long monthBonusSpent;
	private long monthRevenue;//выручка = сумма покупок - потраченный бонус

	public Engine() {
		this.userMap = new HashMap<Long, User>();
	}

	public Engine(float alpha, float beta, long averageAmount, long dispersia, int yRatio, float basicCost) {
		this();
		this.alpha = alpha;
		this.beta = beta;
		this.averageAmount = averageAmount;
		this.dispersia = dispersia;
		this.yRatio = yRatio;
		this.basicCost = basicCost;
	}

	public void init(int initUsers) {
		root = new User(-1);
		for (int i = 0; i < initUsers; i++) {
			User u = new User(i);
			root.addKid(u);
			userMap.put(u.getId(), u);
		}
	}

	public long executeDay(long previousMaxUserId) {
		//RandomUtils.cleanPoisson();
		int workerNum = RandomUtils.nextPoisson/*ForNumberOfWorkers*/(RandomUtils.calculateMju(alpha, userMap.size()));
		List<Long> workers = new ArrayList<Long>();

		long workerId;
		while (workers.size() < workerNum) {
			//workerId = RandomUtils.nextNormal(userMap.size());
			workerId = RandomUtils.nextUniform(userMap.size());
			//if (!workers.contains(workerId) && workerId < userMap.size()) {
				workers.add(workerId);
			//}
		}

		List<Integer> invitees = new ArrayList<Integer>();
//		RandomUtils.cleanPoisson();
//		Double mju = RandomUtils.calculateMju(alpha, workerNum/*userMap.size()*/);
		while (invitees.size() < workerNum) {
			invitees.add(/*RandomUtils.nextPoissonForNumberOfWorkers(mju)*/1);
		}

		User workerUser;
		int workerPos = 0;
		int addedKids, kidsToAdd;
		for (long worker : workers) {
			workerUser = userMap.get(worker);
			addedKids = 0;
			kidsToAdd = invitees.get(workerPos);
			while (addedKids < kidsToAdd) {
				User kid = new User(++previousMaxUserId);
				workerUser.addKid(kid);
				userMap.put(kid.getId(), kid);
				addedKids++;
			}
			workerPos++;
		}
		return previousMaxUserId;
	}

	public void executePurchase(int day) {
		int customerNum = RandomUtils.nextPoisson(RandomUtils.calculateMju(beta, userMap.size()));
		long customerId;
		User customer, customerParent;
		long purchaseAmount;
		long X, Y;
		int depth;
		for (int i = 0; i < customerNum; i++) {
			customerId = RandomUtils.nextUniform(userMap.size());
			customer = userMap.get(customerId);
			purchaseAmount = RandomUtils.nextNormal(averageAmount, dispersia);

			this.totalPurchaseAmount += purchaseAmount;
			this.totalPurchaseNumber++;

			this.monthCost += (long)(purchaseAmount * basicCost);
			this.monthRevenue += purchaseAmount;
			if ((customer.getLastPurchaseDay() - day) > 180) {
				customer.setTotalBonus(0l);// бонусы сгорели
			} else {
				customer.setLastPurchaseDay(day);
				if (customer.getTotalBonus() > purchaseAmount) {
					// оплатили бонусами и ничего не начисляем
					customer.setTotalBonus(customer.getTotalBonus() - purchaseAmount);
					customer.setTotalPurchase(customer.getTotalPurchase() + purchaseAmount);

					this.totalBonusSpent += purchaseAmount;
					this.monthCost += purchaseAmount;
					this.monthBonusSpent += purchaseAmount;
					this.monthRevenue -= purchaseAmount;
				} else {
					this.totalBonusSpent += customer.getTotalBonus();
					this.monthCost += customer.getTotalBonus();
					this.monthBonusSpent += customer.getTotalBonus();
					this.monthRevenue -= customer.getTotalBonus();

					purchaseAmount -= customer.getTotalBonus();// оплатили какими было бонусами

					customer.setTotalBonus(0l);// бонусов больше не осталось

					X = calculateBonus(purchaseAmount, customer.getTotalPurchase());// рассчитали бонус от оставшейся стоимости
					Y = (purchaseAmount * yRatio / 100);

					this.totalBonusGiven += X;
					this.totalBonusGiven += Y;

					customer.setTotalPurchase(customer.getTotalPurchase() + purchaseAmount);
					customer.setTotalBonus(customer.getTotalBonus() + X);
					depth = 1;
					customerParent = customer.getParent();
					while (customerParent.getParent() != null && customerParent.getParent().getId() != -1) {
						Y /= Math.pow(2, depth);
						customerParent.setTotalBonus(customerParent.getTotalBonus() + Y);
						depth++;
						customerParent = customerParent.getParent();
					}
					customer.getParent().setTotalBonus(customer.getParent().getTotalBonus() + Y);
				}
			}
		}
		if (day % 30 == 0) {
			cleanMonthlyValues();
		}
	}

	public void setAlpha(float alpha) {
		this.alpha = alpha;
	}

	public User getRoot() {
		return root;
	}

	public long getMaxUserId() {
		return this.userMap.size() - 1;
	}

	public Map<Long, User> getUserMap() {
		return userMap;
	}

	private long calculateBonus(long currentPurchase, long totalPurchase) {
		if (totalPurchase < 3000l) {
			return 0l;
		} else if (totalPurchase >= 3000l && totalPurchase < 25000l) {
			return 4 * (currentPurchase / 100);
		} else if (totalPurchase >= 25000l && totalPurchase < 50000l) {
			return 6 * (currentPurchase / 100);
		} else if (totalPurchase >= 50000l && totalPurchase < 80000l) {
			return 8 * (currentPurchase / 100);
		} else {
			return 10 * (currentPurchase / 100);
		}
	}

	public long getTotalPurchaseAmount() {
		return totalPurchaseAmount;
	}

	public long getTotalPurchaseNumber() {
		return totalPurchaseNumber;
	}

	public long getTotalBonusGiven() {
		return totalBonusGiven;
	}

	public long getTotalBonusSpent() {
		return totalBonusSpent;
	}

	private void cleanMonthlyValues() {
		monthBonusSpent = 0;
		monthCost = 0;
		monthRevenue = 0;
	}

	public long getMonthCost() {
		return monthCost;
	}

	public long getMonthBonusSpent() {
		return monthBonusSpent;
	}

	public long getMonthRevenue() {
		return monthRevenue;
	}

}
